#include <Kharlia/Engine/Common.h>
#include <Kharlia/Engine/Resource.h>
#include <Kharlia/Engine/Init.h>
#include <Kharlia/Engine/Engine.h>
#include <Kharlia/Engine/Client.h>
#include <Kharlia/Core/ObjectUtil.h>

#include <CEGUIRenderer.h>

namespace kharlia { namespace engine {

void FResourceManager::Register(shared_ptr<FResource> res) {
    if (res->mManager.lock()) {
        format msg("resource already registered with a manager: %1%");
        msg % extract<char const*>(res->GetName());
        throw RuntimeError(msg);
    }
    if (GetResource(res->GetName())) {
        format msg("resource already registered with name: %1%");
        msg % extract<char const*>(res->GetName());
        throw RuntimeError(msg);
    }
    if (IsManagingPyObject(res))
        res = res->shared_from_this();
    mResources[res->GetName()] = res;
    res->mManager = shared_from_this();
    GetLoggerPtr()->Debug(str("registered resource: ") + res->GetName());
}

void FResourceManager::Unregister(FResource* res) {
    assert(res->mManager.lock().get() == this);
    GetLoggerPtr()->Debug(str("unregistering resource: ") + res->GetName());
    if (mResources.erase(res->GetName()) != 1) {
        throw RuntimeError("attempting to unregister unknown resource");
    }
    res->mManager.reset();
}

void FResourceManager::Unregister(shared_ptr<FResource> res) {
    Unregister(res.get());
}

void FResourceManager::UnloadAll() {
    for (auto it = mResources.begin(); it != mResources.end(); it++) {
        shared_ptr<FResource> r = it->second.lock();
        if (r && r->GetState() != RS_UNLOADED) {
            r->Unload();
            assert(r->GetState() == RS_UNLOADED);
        }
    }
}

void FResourceManager::UnregisterAll() {
    for (auto it = mResources.begin(); it != mResources.end(); it++) {
        shared_ptr<FResource> r = it->second.lock();
        if (r)
            r->mManager.reset();
    }
    mResources.clear();
}

shared_ptr<FResource> FResourceManager::CreateResource(object cls, str name) {
    shared_ptr<FResource> r = GetResource(name);
    if (!r) {
        r = extract<shared_ptr<FResource>>(cls(name));
        Register(r);
    }
    return r;
}

FResource::FResource(str name):
    mManager(), mName(name), mState(RS_UNLOADED)
{
}

FResource::~FResource() {
    auto manager = mManager.lock();
    if (manager)
        manager->Unregister(this);
}

tuple FResourcePickler::getinitargs(FResource& self) {
    return py::make_tuple(self.mName);
}

tuple FResourcePickler::getstate(FResource& self) {
    return py::make_tuple(self.mState);
}

void FResourcePickler::setstate(FResource& self, tuple state) {
    self.mState = extract<EResourceState>(state[0]);
}

FTexture::FTexture(str name):
    FResource(name), mIntTexture(nullptr)
{
}

FTexture::~FTexture() {
    DestroyIntTexture();
}

bool FTexture::GetCanLoad() const {
    // If a client is set then we have what we need to perform a load
    return GetEnginePtr() && GetEnginePtr()->GetClient();
}

void FTexture::Load() {
    if (mState == RS_LOADED)
        return;

    if (mState != RS_UNLOADED)
        throw RuntimeError("unknown state");

    assert(!mIntTexture);

    if (!GetCanLoad())
        throw RuntimeError("not ready for loading");

    mState = RS_LOADING;

    // Assume texture name is the path
    GetEnginePtr()->GetClient()->LoadTexture(*this, mName);

    assert(GetIsValid());
    assert(GetIsLoaded());
}

void FTexture::Unload() {
    DestroyIntTexture();
}

void FTexture::SetIntTexture(CEGUI::Texture* tex) {
    mIntTexture = tex;
    mState = tex ? RS_LOADED: RS_UNLOADED;
}

void FTexture::DestroyIntTexture() {
    if (!mIntTexture)
        return;

    TPtr<CClient> client = GetEnginePtr()->GetClient();
    assert(client);
    mState = RS_UNLOADING;
    ui::Renderer* rn = client->GetUIRenderer();
    if (rn)
        rn->destroyTexture(*mIntTexture);
    mIntTexture = nullptr;
    mState = RS_UNLOADED;
}

tuple FTexturePickler::getinitargs(FTexture& self) {
    return FResourcePickler::getinitargs(self);
}

tuple FTexturePickler::getstate(FTexture& self) {
    return py::make_tuple();
}

void FTexturePickler::setstate(FTexture& self, tuple state) {
}



CSpriteAniDef::CSpriteAniDef(PyObject* self, str name):
    CObject(self),
    FResource(name),
    mFrameLists(),
    mShouldLoop(false)
{
}

CSpriteAniDef::~CSpriteAniDef() {
}

void CSpriteAniDef::Load() {
    if (mState == RS_LOADED)
        return;

    assert(mState == RS_UNLOADED);
    
    if (!CanLoad())
        throw RuntimeError("not ready for loading");

    mState = RS_LOADING;

    dict cfg = extract<dict>(LoadDefFile(unicode(mName)));
    mShouldLoop = extract<bool>(cfg["ShouldLoop"]);
    FrameList flist;
    Frame frame;
    PY_FOR(flistcfg, cfg["FrameLists"].attr("itervalues")()) {
        PY_FOR(framecfg, flistcfg.attr("itervalues")()) {
            frame.Rect = extract<FRectI>(framecfg[0]);
            frame.Time = extract<float>(framecfg[1]);
            flist.push_back(frame);
        }
        mFrameLists.push_back(std::move(flist));
        assert(flist.size() == 0);
    }

    mState = RS_LOADED;
}

void CSpriteAniDef::Unload() {
    if (mState == RS_UNLOADED)
        return;

    assert(mState == RS_LOADED);
    mState = RS_UNLOADING;

    mShouldLoop = false;
    mFrameLists.clear();

    mState = RS_UNLOADED;
}

bool CSpriteAniDef::CanLoad() const {
    return GetEnginePtr();
}

tuple CSpriteAniDef::GetFramePy(uint16 s, uint16 i) const {
    Frame const& f = mFrameLists.at(s).at(i);
    return py::make_tuple(f.Rect, f.Time);
}

void _InitResourceClasses() {
    _InitResourceBindings();
}


}}